﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;

namespace TestPlanWordWriter.Common.Word
{
    public class WordPlanContentBuilder
    {
        private TestPlanDocWriterSettings _settings;
        private const int FIRST_COLUMN_WIDTH_IN_CM = 2;

        public WordPlanContentBuilder(TestPlanDocWriterSettings settings)
        {
            _settings = settings;
        }

        public void BuildPackageContent(TestPlanInfo planInfo, string stylesXml, Stream documentStream)
        {
            using (WordprocessingDocument processingDocument = WordprocessingDocument.Create(documentStream, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
            {
                // Add a new main document part.
                processingDocument.AddMainDocumentPart();

                // Create the Document DOM.             
                processingDocument.MainDocumentPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document(new Body());

                Body body = processingDocument.MainDocumentPart.Document.Body;
                StyleDefinitionsPart docStyleDefinitionsPart = processingDocument.MainDocumentPart.AddNewPart<StyleDefinitionsPart>("rId1");

                FillStyleDefinitionsPart(docStyleDefinitionsPart, stylesXml);

                FillPlanContent(planInfo, body);

                processingDocument.Package.Flush();

                // Save changes to the main document part.             
                processingDocument.MainDocumentPart.Document.Save();
            }
        }

        private static void FillStyleDefinitionsPart(StyleDefinitionsPart docStyleDefinitionsPart, string stylesXml)
        {
            OpcStreamBuilder builder = new OpcStreamBuilder(stylesXml);
            using (MemoryStream openDocumentStream = new MemoryStream())
            {
                builder.BuildOpenDocumentStream(openDocumentStream);
                using (WordprocessingDocument sourceDocument = WordprocessingDocument.Open(openDocumentStream, true))
                {
                    docStyleDefinitionsPart.Styles = new Styles();
                    docStyleDefinitionsPart.Styles.Load(sourceDocument.MainDocumentPart.StyleDefinitionsPart);
                }
            }
        }

        private void FillPlanContent(TestPlanInfo planInfo, Body documentBody)
        {
            AppendTitleAndDescriptionParagraph(documentBody, planInfo.Name, planInfo.Description, _settings.TestPlanStyleName, _settings.TestPlanDescriptionStyleName);

            FillSuiteChildrenContent(planInfo.RootSuite, documentBody, 0);
        }

        private void FillSuiteContent(TestSuiteInfo suite, Body documentBody, int suiteLevel)
        {
            string titleStyleName = null;
            string descriptionStyleName = null;
            if (suiteLevel <= _settings.TestSuiteLevelsSettings.Count)
            {
                titleStyleName = _settings.TestSuiteLevelsSettings[suiteLevel - 1].TestSuiteStyleName;
                descriptionStyleName = _settings.TestSuiteLevelsSettings[suiteLevel - 1].TestSuiteDescription;
            }

            AppendTitleAndDescriptionParagraph(documentBody, suite.Title, suite.Description, titleStyleName, descriptionStyleName);

            FillSuiteChildrenContent(suite, documentBody, suiteLevel);
        }

        private void FillSuiteChildrenContent(TestSuiteInfo suite, Body documentBody, int suiteLevel)
        {
            var orderedList = suite.TestCases.OrderBy(x => x.Title);
            foreach (TestCaseInfo testCase in orderedList)
            {
                FillTestCaseContent(testCase, documentBody);
            }

            //Recursive fill
            var orderedSuites = suite.InnerSuites.OrderBy(testSuite => testSuite.Title);
            foreach (TestSuiteInfo innerSuiteInfo in suite.InnerSuites)
            {
                int innerLevel = suiteLevel + 1;
                FillSuiteContent(innerSuiteInfo, documentBody, innerLevel);
            }
        }

        private void AppendTitleAndDescriptionParagraph(Body documentBody, string title, string description, string titleStyleName, string descriptionStyleName)
        {
            Paragraph suiteParagraph = new Paragraph();
            ParagraphProperties suiteParagraphProperties = new ParagraphProperties();

            ParagraphStyleId suiteParagraphStyleId = BuildStyleFromStyleName(titleStyleName);
            suiteParagraphProperties.AppendChild<ParagraphStyleId>(suiteParagraphStyleId);

            suiteParagraph.AppendChild<ParagraphProperties>(suiteParagraphProperties);
            suiteParagraph.AppendChild<Run>(new Run(new Text(title)));

            documentBody.AppendChild<Paragraph>(suiteParagraph);

            if (!string.IsNullOrEmpty(description))
            {
                Paragraph suiteDescParagraph = new Paragraph();

                ParagraphProperties suiteDescParagraphProperties = new ParagraphProperties();
                ParagraphStyleId suiteDescParagraphStyleId = BuildStyleFromStyleName(descriptionStyleName);
                suiteDescParagraphProperties.AppendChild<ParagraphStyleId>(suiteDescParagraphStyleId);

                suiteDescParagraph.AppendChild<ParagraphProperties>(suiteDescParagraphProperties);
                suiteDescParagraph.AppendChild<Run>(new Run(new Text(description)));

                documentBody.AppendChild<Paragraph>(suiteDescParagraph);
            }
        }

        private void FillTestCaseContent(TestCaseInfo testCase, Body documentBody)
        {
            AppendTitleAndDescriptionParagraph(documentBody, testCase.Title, testCase.Description, _settings.TestCaseStyleName, _settings.TestCaseDescriptionStyleName);

            Table stepsTable = new Table();

            TableProperties stepsTableProperties = new TableProperties();
            TableStyle stepsTableStyle = BuildTableStyleFromStyleName(_settings.TestStepsTableStyleName);
            TableWidth stepsTableWidth = new TableWidth() { Width = "5000", Type = TableWidthUnitValues.Pct };

            ////2cm
            float firstColumnWidth = FIRST_COLUMN_WIDTH_IN_CM * 28.3157f;

            TableGrid stepsTableGrid = new TableGrid();
            GridColumn gridColumn1 = new GridColumn() { Width = Convert.ToInt32(firstColumnWidth).ToString() };
            GridColumn gridColumn2 = new GridColumn();
            GridColumn gridColumn3 = new GridColumn();

            stepsTableProperties.Append(stepsTableStyle);
            stepsTableProperties.Append(stepsTableWidth);

            stepsTable.AppendChild<TableProperties>(stepsTableProperties);
            stepsTable.AppendChild<TableGrid>(stepsTableGrid);

            TableCell indexCellHeader = new TableCell(new Paragraph(new Run(new Text(_settings.TableLabel_Step))));
            TableCell titleCellHeader = new TableCell(new Paragraph(new Run(new Text(_settings.TableLabel_Action))));
            TableCell expectedResultCellHeader = new TableCell(new Paragraph(new Run(new Text(_settings.TableLabel_ExpectedResult)))); 
            TableRow headerRow = new TableRow(indexCellHeader, titleCellHeader, expectedResultCellHeader);
            stepsTable.AppendChild<TableRow>(headerRow);

            int i = 1;
            foreach (TestStepInfo stepInfo in testCase.TestSteps)
            {
                TableCell indexCell = new TableCell(new Paragraph(new Run(new Text(i.ToString()))));
                TableCell titleCell = new TableCell(new Paragraph(new Run(new Text(stepInfo.Title))));
                TableCell expectedResultCell = new TableCell(new Paragraph(new Run(new Text(stepInfo.ExpectedResult))));
                i++;
                TableRow stepRow = new TableRow(indexCell, titleCell, expectedResultCell);
                stepsTable.AppendChild<TableRow>(stepRow);
            }

            documentBody.AppendChild<Table>(stepsTable); 
        }

        private TableStyle BuildTableStyleFromStyleName(string styleName)
        {
            TableStyle paragraphStyleId = new TableStyle()
            {
                Val = StyleNameFormatter.ConvertVSTONameToOpenName(styleName)
            };
            return paragraphStyleId;
        }

        private ParagraphStyleId BuildStyleFromStyleName(string styleName)
        {
            ParagraphStyleId paragraphStyleId = new ParagraphStyleId() 
            { 
                Val = StyleNameFormatter.ConvertVSTONameToOpenName(styleName) 
            };
            return paragraphStyleId;
        }
    }
}
